package com.yugao.fintech.framework.desensitization.fieldsensitive;

import com.yugao.fintech.framework.assistant.core.ObjectUtils;
import com.yugao.fintech.framework.assistant.core.exception.BizException;
import com.yugao.fintech.framework.assistant.core.reflection.DefaultReflectorFactory;
import com.yugao.fintech.framework.assistant.core.reflection.MetaObject;
import com.yugao.fintech.framework.assistant.core.reflection.factory.DefaultObjectFactory;
import com.yugao.fintech.framework.assistant.core.reflection.wrapper.DefaultObjectWrapperFactory;
import com.yugao.fintech.framework.desensitization.fieldsensitive.inter.IFieldSensitiveAnnotationGetter;
import com.yugao.fintech.framework.desensitization.fieldsensitive.model.FieldSensitiveAnnotationData;
import com.yugao.fintech.framework.desensitization.fieldsensitive.model.SensitiveClassCache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 敏感信息处理器
 *

 */
@Component
@Slf4j
public class FieldSensitiveHandler {

    @Resource
    private Map<String, IFieldSensitiveAnnotationGetter> fieldSensitiveAnnotationGetterMap;

    private static final Map<Class<?>, SensitiveClassCache> CACHE = new ConcurrentHashMap<>();

    private static final DefaultObjectFactory defaultObjectFactory = new DefaultObjectFactory();
    private static final DefaultObjectWrapperFactory defaultObjectWrapperFactory = new DefaultObjectWrapperFactory();
    private static final DefaultReflectorFactory defaultReflectorFactory = new DefaultReflectorFactory();

    @PostConstruct
    public void init() {

    }


    @SuppressWarnings(value = {"unchecked", "rawtypes"})
    public SensitiveClassCache createCache(Object targetObj) {
        if (Objects.isNull(targetObj)) {
            return null;
        }
        Class<?> targetClass = targetObj.getClass();
        SensitiveClassCache cache = CACHE.get(targetClass);
        if (Objects.nonNull(cache)) {
            return cache;
        }
        synchronized (targetObj.getClass().getName().intern()) {
            cache = CACHE.get(targetClass);
            if (Objects.nonNull(cache)) {
                return cache;
            }

            SensitiveClassCache classCache = new SensitiveClassCache();
            classCache.setClazz(targetClass);
            List<Field> allFields = ObjectUtils.getFieldsWithSuper(targetObj);

            fieldSensitiveAnnotationGetterMap.forEach((eKey, fieldSensitiveAnnotationGetter) -> {
                Class<? extends Annotation> FIELD_ANNO = (Class<? extends Annotation>) ((ParameterizedType) AopUtils.getTargetClass(fieldSensitiveAnnotationGetter)
                        .getGenericInterfaces()[0])
                        .getActualTypeArguments()[0];

                allFields.forEach(f -> {
                    f.setAccessible(true);
                    Annotation annotation = f.getAnnotation(FIELD_ANNO);
                    if (Objects.isNull(annotation)) {
                        return;
                    }

                    FieldSensitiveAnnotationData annoData = fieldSensitiveAnnotationGetter.getData(annotation);
                    if (Objects.isNull(annoData)) {
                        return;
                    }
                    SensitiveClassCache.FieldOfBaseType fieldOfBaseType = new SensitiveClassCache.FieldOfBaseType();
                    fieldOfBaseType.setField(f);
                    fieldOfBaseType.setAnnoData(annoData);
                    try {
                        fieldOfBaseType.setField(f);
                        fieldOfBaseType.setAnnoData(annoData);
                        classCache.getFields().add(fieldOfBaseType);
                    } catch (Exception e1) {
                        log.error("error: ", e1);
                        throw new BizException("数据脱敏异常");
                    }
                });
                log.info("sensitive handler, init class [{}]", targetClass.getSimpleName());
            });
            CACHE.put(targetClass, classCache);
            return classCache;
        }
    }

    @SuppressWarnings("unchecked")
    public void handleField(Object obj) {
        handleField(obj, new ConcurrentHashMap<>());
    }

    /**
     * 完成字段绑定
     */
    @SuppressWarnings("unchecked")
    private void handleField(Object obj, Map<Object, Class<?>> map) {
        if (Objects.isNull(obj)) {
            return;
        }
        if (obj instanceof Collection) {
            handlerCollectionField((Collection<Object>) obj, map);
        } else if (obj.getClass().isArray()) {
            handleArrayField(obj, map);
        } else if (obj instanceof Map) {
            handleMapField((Map<Object, Object>) obj, map);
        } else if (isJavaClass(obj.getClass())) {
        } else {
            handleSingleField(obj, map);
        }
    }

    private void handleMapField(Map<Object, Object> objMap, Map<Object, Class<?>> map) {
        if (Objects.isNull(objMap)) {
            return;
        }
        objMap.forEach((key, value) -> handleField(value, map));
    }

    /**
     * 完成对象中字段绑定
     */
    private void handleObjectField(Object obj, Map<Object, Class<?>> map) {
        if (Objects.isNull(obj)) {
            return;
        }
        List<Field> fields = ObjectUtils.getFieldsWithSuper(obj);
        for (Field field : fields) {
            try {
                int modifiers = field.getModifiers();

                if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)
                        || Modifier.isInterface(modifiers) || Modifier.isAbstract(modifiers)) {
                    continue;
                }
                field.setAccessible(true);
                Object o = field.get(obj);
                handleField(o, map);
            } catch (Exception e) {
                log.error("error: ", e);
            }
        }
    }

    /**
     * 完成单字段绑定, eg:
     * class Test {
     * String name;
     * Long age;
     * }
     */
    private void handleSingleField(Object obj, Map<Object, Class<?>> map) {
        SensitiveClassCache classCache = CACHE.get(obj.getClass());
        if (Objects.isNull(classCache)) {
            classCache = createCache(obj);
            if (Objects.isNull(classCache)) {
                return;
            }
        }
        if (map.containsKey(obj)) {
            return;
        }
        map.put(obj, obj.getClass());

        MetaObject metaObject = MetaObject.forObject(obj, defaultObjectFactory, defaultObjectWrapperFactory, defaultReflectorFactory);
        doSensitiveFieldSet(obj, metaObject, classCache);
        handleObjectField(obj, map);
    }

    private void handleArrayField(Object obj, Map<Object, Class<?>> map) {
        if (Objects.isNull(obj) || map.containsKey(obj)) {
            return;
        }
        map.put(obj, obj.getClass());
        Object[] arr = (Object[]) obj;
        if (arr.length == 0) {
            return;
        }

        for (Object o : arr) {
            handleField(o, map);
        }
    }

    /**
     * 对集合中的每个对象做字段绑定处理
     */
    private void handlerCollectionField(Collection<Object> objs, Map<Object, Class<?>> map) {
        if (CollectionUtils.isEmpty(objs) || objs.isEmpty() || map.containsKey(objs)) {
            return;
        }
        map.put(objs, objs.getClass());
        objs.stream().findFirst()
                .ifPresent(e -> objs.parallelStream().forEach(o -> handleField(o, map)));
    }

    /**
     * 调度数据绑定接口
     *
     * @param metaObject 对象元数据
     * @param classCache 目标类信息缓存
     */
    private void doSensitiveFieldSet(Object obj, MetaObject metaObject, SensitiveClassCache classCache) {
        if (Objects.isNull(obj)) {
            return;
        }
        Set<SensitiveClassCache.FieldOfBaseType> fields = classCache.getFields();
        fields.stream().parallel().forEach(fInfo -> {
            try {
                String name = fInfo.getField().getName();
                if (isJavaClass(fInfo.getField().getType())) {
                    Object value = metaObject.getValue(name);
                    if (Objects.isNull(value)) {
                        return;
                    }
                    metaObject.setValue(name, fInfo.getAnnoData().getDataProcessor().apply(String.valueOf(value)));
                    return;
                }
                log.warn("class [{}] field [{}] not is base type", fInfo.getField().getType().getName(), name);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    private boolean isJavaClass(Class<?> clz) {
        return clz.getClassLoader() == null;
    }
}
